#include "common/TestLanguageFeatures.h"

#include <vector>
#include <iostream>
#include <stdio.h>
#include <vector>
#include <numeric>

#include <boost/pool/object_pool.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread/once.hpp>

#include "graph_support/GraphSupport.h"

#include "graph_support/utils/CollectionsInit.h"
#include "graph_support/utils/test_my_graph/BoostTestUtils.h"

namespace TestBoostObjectPool_namespace
{

class A
{
public:
	A()
		: id ( counter++ )
	{
		//TODO 5 [big task] use boost::call_once
//		boost::call_once( init, initFlag );

		int l = sprintf( logBuffer, "c %d; ", id );
		BOOST_CHECK_GT( l, 0 );
		logBuffer += l;
	}
	A( const char * msg )
		: id ( counter++ )
	{
		int l = sprintf( logBuffer, "c %d [%s]; ", id, msg );
		BOOST_CHECK_GT( l, 0 );
		logBuffer += l;
	}
	~A()
	{
		int l = sprintf( logBuffer, "~ %d; ", id );
		BOOST_CHECK_GT( l, 0 );
		logBuffer += l;
	}

	static char const * getLog()
	{
		return &logBufferStrg[0];
	}

//private:
public:
	static void init()
	{
		counter = 0;
		logBufferStrg.resize( A_BUF_SZ );
		logBuffer = &logBufferStrg[0];
		int l = sprintf( logBuffer, "TestBoostObjectPool; " );
		BOOST_CHECK_GT( l, 0 );
		logBuffer += l;
	}

private:
	static char * logBuffer;
	static std::vector< char > logBufferStrg;
	static const int A_BUF_SZ = 2 << 15;
	static int counter;
//	static boost::once_flag initFlag;
	int id;

};
int A::counter;
//boost::once_flag A::initFlag;
char * A::logBuffer;
std::vector< char > A::logBufferStrg;

}

void TestBoostObjectPool()
{
	using namespace TestBoostObjectPool_namespace;

	A::init();

	{
	A a;
	BOOST_CHECK_EQUAL(
			std::string( "TestBoostObjectPool; " ) +
			"c 0; "
			, A::getLog()
			);
	}
	BOOST_CHECK_EQUAL(
			std::string( "TestBoostObjectPool; " ) +
			"c 0; " +
			"~ 0; "
			, A::getLog()
			);

	{
	std::auto_ptr< A > a( new A() );
	std::auto_ptr< A > b( new A() );
	std::auto_ptr< A > c( new A() );
	BOOST_CHECK_EQUAL(
			std::string( "TestBoostObjectPool; " ) +
			"c 0; " +
			"~ 0; " +
			"c 1; " +
			"c 2; " +
			"c 3; "
			, A::getLog()
			);
	b = std::auto_ptr< A >( NULL );
	a = std::auto_ptr< A >( NULL );
	c = std::auto_ptr< A >( NULL );
	BOOST_CHECK_EQUAL(
			std::string( "TestBoostObjectPool; " ) +
			"c 0; " +
			"~ 0; " +
			"c 1; " +
			"c 2; " +
			"c 3; " +
			"~ 2; " +
			"~ 1; " +
			"~ 3; "
			, A::getLog()
			);
	}

// ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
	A::init();

	{
	boost::object_pool< A > pool;
	int n = 10;
	std::vector< A * > strg;
	for ( int i = 0; i < n; i++ )
	{
		A * a = NULL;
		if ( i < 6)
		{
			a = pool.construct();
		} else
		{
			a = pool.construct( "Hello" );
		}
		if ( i < 3 )
		{
			strg.push_back( a );
		}
	}
	BOOST_CHECK_EQUAL(
			std::string( "TestBoostObjectPool; " ) +
			"c 0; " +
			"c 1; " +
			"c 2; " +
			"c 3; " +
			"c 4; " +
			"c 5; " +
			"c 6 [Hello]; " +
			"c 7 [Hello]; " +
			"c 8 [Hello]; " +
			"c 9 [Hello]; "
			, A::getLog()
			);
	A * b = new ( pool.malloc() ) A( "Hi!" );

	for ( int i = 0; i < strg.size(); i++ )
	{
		pool.destroy( strg[i] );
		strg[i] = 0;
	}
	BOOST_CHECK_EQUAL(
			std::string( "TestBoostObjectPool; " ) +
			"c 0; " +
			"c 1; " +
			"c 2; " +
			"c 3; " +
			"c 4; " +
			"c 5; " +
			"c 6 [Hello]; " +
			"c 7 [Hello]; " +
			"c 8 [Hello]; " +
			"c 9 [Hello]; " +
			"c 10 [Hi!]; " +
			"~ 0; " +
			"~ 1; " +
			"~ 2; "
			, A::getLog()
			);
	}
	BOOST_CHECK_EQUAL(
			std::string( "TestBoostObjectPool; " ) +
			"c 0; " +
			"c 1; " +
			"c 2; " +
			"c 3; " +
			"c 4; " +
			"c 5; " +
			"c 6 [Hello]; " +
			"c 7 [Hello]; " +
			"c 8 [Hello]; " +
			"c 9 [Hello]; " +
			"c 10 [Hi!]; " +
			"~ 0; " +
			"~ 1; " +
			"~ 2; " +
			"~ 3; " +
			"~ 4; " +
			"~ 5; " +
			"~ 6; " +
			"~ 7; " +
			"~ 8; " +
			"~ 9; "
			"~ 10; "
			, A::getLog()
			);
}

void TestComputeCollLength()
{
	using namespace GRAPH_SUPPORT_NS::utils;

	typedef std::plus< int > PlusFn;
	PlusFn plus;
	int a = plus( 3, 5 );
	BOOST_CHECK_EQUAL( 8, a );

	int b = boost::bind( plus, 11, _1 ) ( 12 );
	BOOST_CHECK_EQUAL( 23, b );

	int c = boost::bind( plus, a, _1 ) ( 12 );
	BOOST_CHECK_EQUAL( 20, c );

	std::vector< int > coll = arr2vector< int >( ( const int [] ) { 1, 2, 3, 4 } );
	size_t count = std::accumulate( coll.begin(), coll.end(), 0, boost::bind( std::plus< int >(), _1, 1 ) );
	BOOST_CHECK_EQUAL( 4, count );
	coll.push_back( 100 );
	BOOST_CHECK_EQUAL(
			5,
			std::accumulate( coll.begin(), coll.end(), 0, boost::bind( std::plus< int >(), _1, 1 ) ) );
}

namespace TestSameFn_namespace
{

class IntHold
{
public:
	IntHold( int content )
		: content( content )
	{
	}
	int & getContent()
	{
		return content;
	}
	int content;
};

template < typename T >
T SameFunctor( T t )
{
	return t;
}

}

void TestSameFn()
{
	using namespace TestSameFn_namespace;

	int a = boost::bind( &SameFunctor< int >, _1 ) ( 123 );
	BOOST_CHECK_EQUAL( 123, a );

	IntHold ih( 234 );
	int b = boost::bind( &IntHold::getContent, _1 ) ( ih );
	BOOST_CHECK_EQUAL( 234, b );

	typedef boost::_bi::bind_t<
			int&, boost::_mfi::mf0< int&, IntHold >,
			boost::_bi::list1< boost::arg< 1 > >
			> MegaFn;
	MegaFn megafunctor = boost::bind( &IntHold::getContent, _1 );

	ih.content = 987;
	int c = megafunctor( ih );
	BOOST_CHECK_EQUAL( 987, c );
	int & d = megafunctor( ih );
	BOOST_CHECK_EQUAL( 987, d );
	ih.content = 765;
	BOOST_CHECK_EQUAL( 987, c );
	BOOST_CHECK_EQUAL( 765, d );
}

namespace TestPtrAccessOperator_namespace
{

class FH
{
public:
	FH( std::vector< int > & strg )
		: strg( strg )
		, id( idcount++ )
	{
	}
	FH( const FH & that )
		: strg( that.strg )
		, id( idcount++ )
	{
	}
public:
	void foo()
	{
		strg.push_back( id );
	}
private:
	const FH & operator = ( const FH & that );
private:
	int id;
	std::vector< int > & strg;
private:
	static int idcount;
};
int FH::idcount = 0;

std::string vec2str( const std::vector< int > & v )
{
	std::stringstream ss;
	ss << "[";
	for ( int i = 0; i < v.size(); i++ )
	{
		ss << ( i == 0 ? "" : ", " ) << v[i];
	}
	ss << "]";
	return ss.str();
}

class PtrAccOp
{
public:
	PtrAccOp( std::auto_ptr< FH > & content )
		: content( content )
	{
	}
public:
	std::auto_ptr< FH > & operator -> ()
	{
		return content;
	}
private:
	std::auto_ptr< FH > & content;
};

}

void TestPtrAccessOperator()
{
	using namespace TestPtrAccessOperator_namespace;

	std::vector< int > strg;
	BOOST_CHECK_EQUAL( 0, strg.size() );
	FH a( strg );
	BOOST_CHECK_EQUAL( 0, strg.size() );
	a.foo();
	BOOST_CHECK_EQUAL( 1, strg.size() );
	BOOST_CHECK_EQUAL( std::string( "[0]" ), vec2str( strg ) );

	std::auto_ptr< FH > b( new FH( strg ) );
	b->foo();
	BOOST_CHECK_EQUAL( std::string( "[0, 1]" ), vec2str( strg ) );

	PtrAccOp pao( b );
	pao->foo();
	BOOST_CHECK_EQUAL( std::string( "[0, 1, 1]" ), vec2str( strg ) );
}

namespace TestRTTIOn_namespace
{
	class SUPER
	{
	public:
		virtual ~SUPER() {}
	};

	class A : public virtual SUPER
	{
	public:
		int a;
	};

	class B : public virtual SUPER
	{
	public:
		int b;
	};

	class C : public virtual A, public virtual B
	{
	public:
		int c;
	};

	class D : public A
	{
	public:
		int d;
	};
}

void TestRTTIOn()
{
	using namespace TestRTTIOn_namespace;

	A a;
	B * tryA2B = dynamic_cast< B * >( ( SUPER * ) & a );
	BOOST_CHECK_EQUAL( ( B * ) NULL, tryA2B );

	C c;
	B * tryC2B = dynamic_cast< B * >( ( SUPER * ) & c );
	BOOST_CHECK( tryC2B );
	A * tryC2A = dynamic_cast< A * >( ( SUPER * ) & c );
	BOOST_CHECK( tryC2A );

	BOOST_CHECK_EQUAL( ( SUPER * ) tryC2B, ( SUPER * ) tryC2A );
	BOOST_CHECK_NE( ( void * ) tryC2B, ( void * ) tryC2A ); // 8-) HA-HA !
}
